Символьная отладка программ

Наиболее эффективный способ отладки программ для микроконтроллеров - применение специализированных, профессиональных отладочных средств, среди которых выделяются отладчики-симуляторы и внутрисхемные эмуляторы.

Прежде чем обсуждать возможности, предоставляемые профессиональными отладчиками, целесообразно коснуться выбора компилятора, с помощью которого исходные тексты программ преобразуются в машинный код.

В подавляющем большинстве случаев предпочтительным является программирование на языке высокого уровня (обычно это Си). Использование чистого ассемблера необходимо только в том случае, если к размеру и быстродействию генерируемого кода предъявляются очень жесткие требования. В настоящее время таких случаев становится все меньше, так как всегда можно взять более быстрый микроконтроллер с большим объемом памяти. Кроме того, современные пакеты кросс-средств позволяют легко писать смешанные программы, где часть модулей написана на Си, а наиболее критичные к быстродействию части - на ассемблере.

Компиляторы Си позволяют также вставлять в исходные тексты ассемблерные инструкции.

Перечислим вкратце преимущества программирования на Си по сравнению с программированием на ассемблере.

Чтобы эффективно отлаживать программы, написанные на языке высокого уровня, разработчик должен иметь в своем распоряжении отладочные средства, предоставляющие адекватные возможности по отображению используемых в программе данных, а также по отслеживанию выполнения программы по ее исходному тексту. Для обеспечения таких возможностей необходимы два условия:

Все современные компиляторы и ассемблеры в том или ином виде генерируют символьную информацию, но в настоящее время еще не разработано ее универсального формата, и каждый компилятор генерирует ее в собственном формате. Это создает дополнительные трудности для отладчиков, которые должны уметь “понимать” несколько символьных форматов.

Теперь рассмотрим, как отладчик должен интерпретировать символьную информацию и какие возможности должны в связи с этим предоставляться пользователю.

Отслеживание выполнения программы по ее исходному тексту.

В общем случае, одна строка исходного текста преобразуется компилятором в несколько машинных команд. Даже ассемблерная программа почти всегда содержит макросы, разворачивающиеся при трансляции в несколько инструкций процессора. Отлаживать такую программу по дисассемблеру ее кода неудобно, поэтому компиляторы вставляют в отладочную информацию таблицу номеров строк. Таблица содержит информацию о соответствии номеров строк исходного текста и имен файлов исходного текста абсолютным адресам кода программы. Отладчик отображает на экране исходный текст программы, и, следуя этой таблице, может выполнять программу “по строкам”, выполняя за один шаг все машинные команды, сгенерированные компилятором для текущей строки. Таблица номеров строк позволяет также производить контекстные действия с текстом программы, например, выполнять программу “до курсора”, т.е. до указанного пользователем места в исходном тексте, ставить точки останова на указанные строки и т.п. Контекстные действия удобны тем, что разработчикам не нужно знать соответствующие строкам исходного текста адреса: отладчик сам определит их по таблице. Отладчик должен также знать адреса подпрограмм, функций и меток кода, и уметь находить исходный текст функции по ее имени.

Отображение данных, использующихся в отлаживаемой программе.

Для полноценной отладки разработчику необходимо иметь возможность в любой момент просмотреть данные, которыми манипулирует программа. Отладчик должен уметь отображать любые используемые программой данные в наиболее подходящем виде.

Как правило, разработчики используют в программах именованные данные, т.е. каждому объекту, который используется в программе, присваивается имя. Объекты могут быть разной сложности - от простых ячеек памяти до сложных конструкций языков высокого уровня типа структур, массивов и т.п.

Данные в ассемблерных программах.

В ассемблерных программах используются в основном простые данные, т.е. ячейки памяти. Применяются также массивы. Для правильного отображения простых данных отладчику нужно знать:

Обладая вышеизложенной информацией, отладчик должен, получив от пользователя имя объекта, отобразить его значение в соответствии с типом. Наиболее продвинутые отладчики дополнительно могут отображать остальные атрибуты объекта.

Данные в программах языков высокого уровня.

Отображать объекты, применяемые в языках высого уровня, значительно сложнее ввиду разнообразия структуры объектов, способов их размещения в памяти и областей видимости. Для примеров будем использовать язык Си, как наиболее популярный у разработчиков.

Структура объектов.

Помимо простых переменных разной разрядности, в Си-программах используются также переменные с плавающей точкой, структуры (struct), объединения или союзы (union), указатели, одномерные и многомерные массивы. Массивы могут состоять как из простых объектов, так и из сложных (структур, союзов, указателей).

Использование сложных объектов в программах, безусловно, удобно. Однако, ввиду сложности структуры объектов, крайне желательно иметь возможность ее адекватного отображения на этапе отладки. В отладчиках фирмы “Фитон” сложные объекты могут отображаться как в сжатом (список значений элементов), так и в развернутом виде с указанием адреса, значения и типа каждого элемента массива и/или члена структуры. Реализация указателей в разных компиляторах различна. То, что микроконтроллер обычно имеет несколько адресных пространств, создает дополнительные трудности, т.к. при работе с указателем должно быть известно, помимо адреса, и адресное пространство, куда указывает указатель. В некоторых реализациях идентификатор адресного пространства является составной частью значения указателя, в других компилятор заранее “знает” это и генерирует соответствующий код. Кроме этого, компонента адреса в указателе может быть размером от 8 до 32 бит. При отображении значений указателей отладчик должен знать все детали их реализации в каждом компиляторе.

Способы размещения объектов в памяти.

Помимо статических объектов, адрес которых не изменяется за время выполнения программы, в программе, написанной на языке высокого уровня, могут существовать так называемые автоматические объекты, память под которые временно отводится в стеке микроконтроллера. Адреса автоматических объектов не абсолютны, а определяются динамически на этапе выполнения программы. Обычно эти адреса отсчитываются от текущего значения некоторой статической переменной, называемой указателем фрейма стека (Base Pointer, BP). Так как значение указателя фрейма стека формируется программой динамически на этапе выполнения, то значения автоматических объектов доступны только в пределах их области видимости, т.е. при правильном значении указателя фрейма стека. Отладчик при отображении значений автоматических объектов должен “знать” способ, которым определяются адреса, а также отслеживать правильность значения BP.

Возможно также временное размещение переменных в регистрах микроконтроллера. В этом случае отладчик должен знать”, какие переменные в каких регистрах размещены и в течение какого времени. И, наконец, часто встречается ситуация, когда один и тот же объект за время своей жизни меняет способ размещения в памяти, причем не один раз. Это может происходить, например, когда функция получает один или несколько параметров в регистрах, а затем перемещает их в стек.

Область видимости объекта.

Как и в ассемблерных программах, в программах, написанных на Си, существуют глобальные объекты, доступные по имени из любого модуля, и объекты, локальные в модуле (эти объекты объявляются как “static”). Однако, автоматические и регистровые переменные создают отладчикам дополнительные трудности при отображении их значений. Дело в том, что, во-первых, время жизни автоматического объекта ограничено его областью видимости, а во-вторых, охватывающие области видимости могут иметь свои автоматические объекты с теми же именами. Проиллюстрируем это на примере функции, имеющей несколько вложенных областей видимости:

void f(int a)
{
long b;
if (a == 0) a++;
for (b = a * 2; b < 100; b++)
{
long c;
long a = b / 3;
if (a == 0) a++;
for (c = a; c < 10; c++)
{
char a = c == 9? 1 : 0;
f1(a);
}
f2(a);
}
f3(a);
}

Переменная с именем “a” существует все время, пока выполняется функция f, но в зависимости от того, какая часть функции выполняется, имя “a” обозначает разные переменные. При трассировке функции f отладчик должен в зависимости от того, какая переменная “активна”, правильно показывать ее значение.

Создавая программу, разработчик не заботится о деталях реализации понятий, которые он использовал в программе. Оперируя “само собой разумеющимися” категориями, разработчики зачастую не подозревают, как сложно было реализовать их разработчикам компиляторов и отладчиков. Разработчикам последних приходится решать задачи совмещения в одной оболочке одновременно простого и интуитивного интерфейса, богатства функциональных возможностей и детальной проработки всего, что связано с реализацией особенностей архитектуры и функционирования конкретного микроконтроллера. Если отладчик не предоставляет разработчику средств отладки, адекватных сложности решаемой задачи, то разработчик неизбежно теряет в производительности. Кому из нас не приходилось тратить часы и дни в поисках собственной опечатки в исходном тексте?!

Published by NewIT Labs
NEW IT Labs